package com.leo.boot.jpa.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.SneakyThrows;
import org.hibernate.dialect.H2Dialect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mapping.model.CamelCaseSplittingFieldNamingStrategy;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;

import static org.hibernate.cfg.AvailableSettings.DIALECT;
import static org.hibernate.cfg.AvailableSettings.IMPLICIT_NAMING_STRATEGY;
import static org.hibernate.cfg.AvailableSettings.PHYSICAL_NAMING_STRATEGY;

@Profile("!h2")
@ConditionalOnClass(JpaRepository.class)
@Configuration
@EnableJpaAuditing
@EnableConfigurationProperties
@EnableJpaRepositories(
        basePackages = "com.leo.boot.jpa",
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager"
)
public class JPAConfig {

    @Bean
    AuditorAware<String> auditorAware() {
        return () -> Optional.of(UUID.randomUUID().toString());
    }

    @Bean
    @ConfigurationProperties("spring.datasource")
    DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    @SneakyThrows
    @Bean
    DataSource dataSource() {
        DataSourceProperties dataSourceProperties = dataSourceProperties();
        DataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().build();
        if (dataSource instanceof DruidDataSource) {
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;
            druidDataSource.setName("h2");
            druidDataSource.setFilters("stat,slf4j");
            druidDataSource.setSocketTimeout(60000);
        }
        return dataSource;
    }

    @Bean
    EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(true);

        Properties jpaProperties = new Properties();
        jpaProperties.setProperty(IMPLICIT_NAMING_STRATEGY, SpringImplicitNamingStrategy.class.getName());
        jpaProperties.setProperty(PHYSICAL_NAMING_STRATEGY, CamelCaseSplittingFieldNamingStrategy.class.getName());
        jpaProperties.setProperty(DIALECT, H2Dialect.class.getName());

        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setDataSource(dataSource());
        entityManager.setJpaVendorAdapter(vendorAdapter);
        entityManager.setJpaProperties(jpaProperties);
        entityManager.setPackagesToScan("com.leo.boot.jpa");
        entityManager.afterPropertiesSet();
        return entityManager.getObject();
    }

    @Bean
    PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager(entityManagerFactory());
    }
}
